﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;

namespace LINQToolKit.Query.Rules
{
    public sealed class QueryRule
    {
        public IDictionary<Type, TypeRule> Types { get; set; }

        public QueryRule()
        {
            this.Types = new Dictionary<Type, TypeRule>();
        }

        public PropertyRule GetPropertyRule(Type entityType, string name, string oper)
        {
            entityType.AssertNull("entityType");
            name.AssertNull("name");
            oper.AssertNull("oper");

            if (this.Types.ContainsKey(entityType))
            {
                var rule = this.Types[entityType];
                var key = oper + name;

                if (rule.Properties.ContainsKey(key))
                {
                    return rule.Properties[key];   
                }
            }

            return null;
        }

        public MethodRule GetMethodRule(Type entityType, string name)
        {
            entityType.AssertNull("entityType");
            name.AssertNull("name");

            if (this.Types.ContainsKey(entityType))
            {
                var rule = this.Types[entityType];

                if (rule.Methods.ContainsKey(name))
                {
                    return rule.Methods[name];                    
                }
            }

            return null;
        }

        private static QueryRule LoadElement(XElement doc)
        {
            QueryRule queryRule = new QueryRule();

            foreach (var t in doc.Elements("type"))
            {
                var type = new TypeRule();
                type.Type = t.Attribute("name").ToType(true);
                queryRule.Types.Add(type.Type, type);
                var queries =
                    from q in t.Attributes()
                    where q.Name != "type"
                    select q;

                foreach (var q in queries)
                {
                    type.Queries.Add(q.Name.LocalName, q.Value);
                }

                foreach (var property in t.Elements("property"))
                {
                    PropertyRule rule = new PropertyRule
                    {
                        Name = property.Attribute("name").GetString(),
                        Operator = property.Attribute("operator").GetString(),
                        QueryUri = property.Attribute("queryUri").GetString(),
                    };

                    rule.Operator = string.IsNullOrEmpty(rule.Operator) ? "=" : rule.Operator;
                    rule.QueryUri = rule.QueryUri.Contains("?") ? rule.QueryUri : rule.QueryUri + "?";
                    type.Properties.Add(rule.Operator + rule.Name, rule);
                }

                foreach (var method in t.Elements("method"))
                {
                    MethodRule rule = new MethodRule
                    {
                        Name = method.Attribute("name").GetString(),
                        QueryUri = method.Attribute("queryUri").GetString(),
                    };

                    rule.QueryUri = rule.QueryUri.Contains("?") ? rule.QueryUri : rule.QueryUri + "?";
                    type.Methods.Add(rule.Name, rule);
                }
            }

            return queryRule;
        }

        public static QueryRule Load(string path)
        {
            path.AssertNull("path");

            if (!File.Exists(path))
            {
                throw new FileNotFoundException(path);
            }

            return QueryRule.LoadElement(XElement.Load(path));
        }

        public static QueryRule LoadXml(string xml)
        {
            xml.AssertNull("xml");

            return QueryRule.LoadElement(XElement.Parse(xml));
        }

        public static QueryRule Default
        {
            get 
            {
                string path = Path.Combine(
                    AppDomain.CurrentDomain.BaseDirectory, "queries.config");

                return QueryRule.Load(path);
            }
        }
    }
}
